﻿#region NeoPatterns
// 
// NeoPatterns framework is released under LGPLv3 license.
// See http://www.opensource.org/licenses/lgpl-3.0.html for more details.
// 
// Please visit http://neopatterns.wikidot.com for more information about NeoPatterns
// 
// Copyright (c) Pascal Craponne 2008
// 
#endregion

using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using NeoPatterns.Exception;
using NeoPatterns.Patterns.Adapter;
using NeoPatterns.Utility;

namespace NeoPatterns.Patterns.UniformAccessor
{
    public class ReflectionUniformAccessor : IUniformAccessor
    {
        /// <summary>
        /// Represents all members from a given type present in a given class type
        /// </summary>
        protected class TypeMember
        {
            /// <summary>
            /// Class type containing all members
            /// </summary>
            public Type ClassType { get; private set; }
            /// <summary>
            /// All member types
            /// </summary>
            public Type MemberType { get; private set; }

            public override bool Equals(object obj)
            {
                var typeMember = obj as TypeMember;
                if (typeMember == null)
                    return false;
                return ClassType == typeMember.ClassType && MemberType == typeMember.MemberType;
            }

            public override int GetHashCode()
            {
                return ClassType.GetHashCode() ^ MemberType.GetHashCode();
            }

            public TypeMember(Type classType, Type memberType)
            {
                ClassType = classType;
                MemberType = memberType;
            }
        }

        /// <summary>
        /// Full definition of a member
        /// - MemberInfo (to access field/property)
        /// - For adapters, IArrayAdapter implementation
        /// </summary>
        protected class MemberAccessor
        {
            /// <summary>
            /// The field/property to be accessed
            /// </summary>
            public MemberInfo MemberInfo { get; private set; }
            /// <summary>
            /// If non-null, an IArrayAdapter implementation type
            /// </summary>
            public Type ArrayAdapterType { get; set; }

            public MemberAccessor(MemberInfo memberInfo)
            {
                MemberInfo = memberInfo;
            }
        }

        /// <summary>
        /// Contains all members for a given type
        /// </summary>
        private readonly IDictionary<TypeMember, IDictionary<string, MemberAccessor>> typeMembers =
            new Dictionary<TypeMember, IDictionary<string, MemberAccessor>>();

        /// <summary>
        /// Contains non-generic registered collections (that could not be identified),
        /// related to a Type
        /// </summary>
        private readonly IDictionary<TypeMember, MemberInfo> registeredCollectionsMembers =
            new Dictionary<TypeMember, MemberInfo>();

        /// <summary>
        /// Specialized collections and their wrappers are registered here
        /// </summary>
        private readonly IDictionary<Type, Type> arrayAdaptersByType = new Dictionary<Type, Type>();

        /// <summary>
        /// Checks if a given type can match an expected type
        /// True for:
        /// - type or inherited
        /// - typed collections
        /// </summary>
        /// <param name="memberType"></param>
        /// <param name="expectedType"></param>
        /// <returns></returns>
        protected virtual bool IsTypeMatch(Type memberType, Type expectedType)
        {
            // inherited
            if (expectedType.IsAssignableFrom(memberType))
                return true;

            return false;
        }

        /// <summary>
        /// Checks if a given type can match an expected type
        /// True for:
        /// - type or inherited
        /// - typed collections
        /// </summary>
        /// <param name="memberType"></param>
        /// <param name="expectedType"></param>
        /// <returns></returns>
        protected virtual bool IsCollectionTypeMatch(Type memberType, Type expectedType)
        {
            // collection (ICollection<T> ... if T is of member type)
            if (memberType.IsGenericType
                && typeof(IList<object>).IsAssignableFrom(memberType.GetGenericTypeDefinition().MakeGenericType(typeof(object))))
            {
                return IsTypeMatch(memberType.GetGenericArguments()[0], expectedType);
            }

            return false;
        }

        /// <summary>
        /// Collects automatically all members related to a type
        /// </summary>
        /// <returns></returns>
        protected virtual IDictionary<string, MemberAccessor> EnumerateMembers(TypeMember typeMember)
        {
            var members = new Dictionary<string, MemberAccessor>();
            foreach (var member in typeMember.ClassType.GetMembers(BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance))
            {
                if (member is FieldInfo || member is PropertyInfo)
                {
                    var memberType = member.GetMemberType();
                    // check for simple type
                    if (IsTypeMatch(memberType, typeMember.MemberType))
                        members[member.Name] = new MemberAccessor(member);
                    // check for collections
                    else if (IsCollectionTypeMatch(memberType, typeMember.MemberType))
                        members[member.Name] = new MemberAccessor(member) { ArrayAdapterType = typeof(ListAdapter) };
                    // check for global collections
                    else if (arrayAdaptersByType.ContainsKey(memberType))
                        members[member.Name] = new MemberAccessor(member) { ArrayAdapterType = arrayAdaptersByType[memberType] };
                }
            }
            return members;
        }

        /// <summary>
        /// When registering a class, checks if some non-generic collections were registered for a parent
        /// If yes, add them to the list
        /// </summary>
        /// <param name="members"></param>
        /// <param name="type"></param>
        protected virtual void AddRegisteredMembers(IDictionary<string, MemberAccessor> members, Type type)
        {
            foreach (var registeredTypeMember in from rm in registeredCollectionsMembers
                                                 where rm.Key.ClassType.IsAssignableFrom(type)
                                                 select rm)
            {
                members[registeredTypeMember.Value.Name] = new MemberAccessor(registeredTypeMember.Value) { ArrayAdapterType = typeof(ListAdapter) };
            }
        }

        /// <summary>
        /// Returns all MemberNames for a given class/member type pair (TypeMember)
        /// </summary>
        /// <param name="typeMember"></param>
        /// <returns></returns>
        protected virtual IDictionary<string, MemberAccessor> GetMembers(TypeMember typeMember)
        {
            lock (typeMembers)
            {
                IDictionary<string, MemberAccessor> members;
                if (!typeMembers.TryGetValue(typeMember, out members))
                {
                    members = EnumerateMembers(typeMember);
                    AddRegisteredMembers(members, typeMember.ClassType);
                    typeMembers[typeMember] = members;
                }
                return members;
            }
        }

        /// <summary>
        /// Returns all member accessors matching the current type and member name
        /// </summary>
        /// <param name="classType"></param>
        /// <param name="memberName"></param>
        /// <returns></returns>
        protected IEnumerable<MemberAccessor> GetMemberAccessors(Type classType, string memberName)
        {
            foreach (var typeMember in typeMembers)
            {
                if (typeMember.Key.ClassType != classType)
                    continue;
                foreach (var memberAccessor in typeMember.Value)
                {
                    yield return memberAccessor.Value;
                }
            }
        }

        /// <summary>
        /// Returns an adapter type to the array
        /// Checks consistency (all members must return the same type)
        /// </summary>
        /// <param name="classType"></param>
        /// <param name="memberName"></param>
        /// <returns></returns>
        protected Type GetMemberArrayAdapterType(Type classType, string memberName)
        {
            bool first = true;
            Type arrayAdapterType = null;
            foreach (var memberAccessor in GetMemberAccessors(classType, memberName))
            {
                if (first)
                {
                    arrayAdapterType = memberAccessor.ArrayAdapterType;
                    first = false;
                }
                else
                {
                    if (arrayAdapterType != memberAccessor.ArrayAdapterType)
                        throw new ConsistencyException();
                }
            }
            return arrayAdapterType;
        }

        /// <summary>
        /// Creates an IArrayAdapter implementation if the member is identified as a list
        /// </summary>
        /// <param name="holder"></param>
        /// <param name="memberName"></param>
        /// <returns></returns>
        protected IArrayAdapter GetMemberArrayAdapter(object holder, string memberName)
        {
            var classType = holder.GetType();
            var arrayAdapterType = GetMemberArrayAdapterType(classType, memberName);
            // null means no collection, so no instance
            if (arrayAdapterType == null)
                return null;
            var arrayAdapter = (IArrayAdapter)Activator.CreateInstance(arrayAdapterType);
            // after instance is created, we also initialize the Array property
            arrayAdapter.Array = GetMemberInfo(holder, new MemberRef(memberName)).GetMemberValue(holder);
            return arrayAdapter;
        }

        /// <summary>
        /// Registers a non-generic collection (which can not be automatically identified)
        /// </summary>
        /// <param name="memberType">The expected type member</param>
        /// <param name="memberInfo"></param>
        public void RegisterMemberCollection(Type memberType, MemberInfo memberInfo)
        {
            var typeMember = new TypeMember(memberInfo.DeclaringType, memberType);
            var members = GetMembers(typeMember); // get current members
            // and add this one
            members[memberInfo.Name] = new MemberAccessor(memberInfo) { ArrayAdapterType = typeof(ListAdapter) };
            registeredCollectionsMembers[typeMember] = memberInfo;
        }

        /// <summary>
        /// Registers a collection type, to be automatically identified and handled
        /// </summary>
        /// <param name="memberType"></param>
        /// <param name="arrayAdapterType"></param>
        public void RegisterSpecializedCollection(Type memberType, Type arrayAdapterType)
        {
            // since we add a new type, we just clear all previously analyzed type
            // (could be better)
            typeMembers.Clear();
            arrayAdaptersByType[memberType] = arrayAdapterType;
        }

        /// <summary>
        /// Returns a MemberInfo given a MemberRef
        /// </summary>
        /// <param name="holder"></param>
        /// <param name="memberRef"></param>
        /// <returns></returns>
        protected virtual MemberInfo GetMemberInfo(object holder, MemberRef memberRef)
        {
            var memberInfo = holder.GetType().GetMember(memberRef.Name, BindingFlags.Public | BindingFlags.FlattenHierarchy | BindingFlags.Instance).Single();
            return memberInfo;
        }

        /// <summary>
        /// Enumerates all members with a given type for a given object
        /// </summary>
        /// <typeparam name="TMember"></typeparam>
        /// <param name="holder"></param>
        /// <returns></returns>
        public IEnumerable<KeyValuePair<MemberRef, TMember>> Enumerate<TMember>(object holder)
        {
            var members = GetMembers(new TypeMember(holder.GetType(), typeof(TMember)));
            foreach (var member in members)
            {
                var memberObject = member.Value.MemberInfo.GetMemberValue(holder);
                if (member.Value.ArrayAdapterType != null)
                {
                    // the adapter allows to enumerate through some diverse forms of lists
                    var listAdapter = (IArrayAdapter)Activator.CreateInstance(member.Value.ArrayAdapterType);
                    listAdapter.Array = memberObject;
                    var count = listAdapter.Count;
                    for (int itemIndex = 0; itemIndex < count; itemIndex++)
                    {
                        var itemObject = listAdapter[itemIndex];
                        var item = (TMember)itemObject;
                        var memberName = new MemberRef(member.Key, itemIndex);
                        yield return new KeyValuePair<MemberRef, TMember>(memberName, item);
                    }
                }
                else
                {
                    // simple object
                    var memberValue = (TMember)memberObject;
                    yield return new KeyValuePair<MemberRef, TMember>(member.Key, memberValue);
                }
            }
        }
    }
}
